﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Markup;
using System.ComponentModel;
using System.Windows.Data;
using SDF.Pagination;


namespace SDF
{
    [ContentProperty("Blocks")]
    public class Figure : SmartInline, IBlockContainer
    {
        #region Constructor

        public Figure()
        {
            Fragment = new Fragment() { Margin = new Thickness(0) };

            SmartLogicalTreeHelper.AddChild(this, Fragment);
        }

        #endregion

        #region Dependency Properties
        
        public static readonly DependencyProperty MarginProperty = SmartBlock.MarginProperty.AddOwner(typeof(Figure));
        public static readonly DependencyProperty PaddingProperty = SmartBlock.PaddingProperty.AddOwner(typeof(Figure));

        public static readonly DependencyProperty AlignmentProperty = DependencyProperty.Register(
            "Alignment",
            typeof(FigureAlignment),
            typeof(Figure),
            new FrameworkPropertyMetadata(FigureAlignment.Left));


        public static readonly DependencyProperty HeightProperty = DependencyProperty.Register(
            "Height",
            typeof(double),
            typeof(Figure),
            new FrameworkPropertyMetadata(double.NaN),
            new ValidateValueCallback(ValidateHeight));

        private static bool ValidateHeight(object value)
        {
            var height = (double)value;
            if (height < 0)
                return false;

            if ((!double.IsNaN(height)) && (height > MAX_HEIGHT))
                return false;

            return true;
        }

        private const double MAX_HEIGHT = 3579139.4066666667;


        public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(
            "Width",
            typeof(FigureWidth),
            typeof(Figure),
            new FrameworkPropertyMetadata(new FigureWidth(double.NaN)));

        
        #endregion

        #region Properties

        private Border Border { get; set; }

        private Fragment Fragment { get; set; }

        public FigureAlignment Alignment
        {
            get { return (FigureAlignment)GetValue(AlignmentProperty); }
            set { SetValue(AlignmentProperty, value); }
        }

        public SmartBlockCollection Blocks
        {
            get { return Fragment.Blocks; }
        }

        public double Height
        {
            get { return (double)GetValue(HeightProperty); }
            set { SetValue(HeightProperty, value); }
        }

        public Thickness Margin
        {
            get { return (Thickness)GetValue(MarginProperty); }
            set { SetValue(MarginProperty, value); }
        }

        public Thickness Padding
        {
            get { return (Thickness)GetValue(PaddingProperty); }
            set { SetValue(PaddingProperty, value); }
        }

        public FigureWidth Width
        {
            get { return (FigureWidth)GetValue(WidthProperty); }
            set { SetValue(WidthProperty, value); }
        }

        public Size DesiredSize
        {
            get { return Border.DesiredSize; }
        }

        public Point Location
        {
            get { return new Point(Left, Top); }
        }

        public Rect Bounds
        {
            get { return new Rect(Location, DesiredSize); }
        }

        public double Left
        {
            get { return Canvas.GetLeft(Border); }
            set { Canvas.SetLeft(Border, value); }
        }

        public double Top
        {
            get { return Canvas.GetTop(Border); }
            set { Canvas.SetTop(Border, value); }
        }

        #endregion

        #region OnPropertyChanged Method

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);

            if (e.Property == Figure.BackgroundProperty)
            {
                if (Border != null)
                    Border.Background = this.Background;
            }
            else if (e.Property == Figure.FontFamilyProperty)
            {
                Fragment.FontFamily = this.FontFamily;
                UpdateVisualMargin(Border);
                UpdateVisualPadding(Fragment);
            }
            else if (e.Property == Figure.FontSizeProperty)
            {
                Fragment.FontSize = this.FontSize;
                UpdateVisualMargin(Border);
                UpdateVisualPadding(Fragment);
            }
            else if (e.Property == Figure.FontStretchProperty)
            {
                Fragment.FontStretch = this.FontStretch;
            }
            else if (e.Property == Figure.FontStyleProperty)
            {
                Fragment.FontStyle = this.FontStyle;
            }
            else if (e.Property == Figure.FontWeightProperty)
            {
                Fragment.FontWeight = this.FontWeight;
            }
            else if (e.Property == Figure.ForegroundProperty)
            {
                Fragment.Foreground = this.Foreground;
            }
            else if (e.Property == Figure.TextEffectsProperty)
            {
                Fragment.TextEffects = this.TextEffects;
            }
            else if (e.Property == Figure.PaddingProperty)
            {
                UpdateVisualPadding(Fragment);
            }
            else if (e.Property == Figure.MarginProperty)
            {
                UpdateVisualMargin(Border);
            }
        }

        private void UpdateVisualMargin(Border border)
        {
            if (border != null)
            {
                Thickness result = this.Margin;
                if (double.IsNaN(result.Bottom))
                    result.Bottom = this.LineHeight * 0.5;
                if (double.IsNaN(result.Left))
                    result.Left = this.LineHeight * 0.5;
                if (double.IsNaN(result.Right))
                    result.Right = this.LineHeight * 0.5;
                if (double.IsNaN(result.Top))
                    result.Top = this.LineHeight * 0.5;

                border.Margin = result;
            }
        }

        private void UpdateVisualPadding(Fragment fragment)
        {
            if (fragment != null)
            {
                Thickness result = this.Padding;
                if (double.IsNaN(result.Bottom))
                    result.Bottom = this.LineHeight * 0.5;
                if (double.IsNaN(result.Left))
                    result.Left = this.LineHeight * 0.5;
                if (double.IsNaN(result.Right))
                    result.Right = this.LineHeight * 0.5;
                if (double.IsNaN(result.Top))
                    result.Top = this.LineHeight * 0.5;

                fragment.Padding = result;
            }
        }
        
        #endregion

        #region Measure

        public void Measure(Size size)
        {
            Border.Height = this.Height;

            if (!double.IsInfinity(size.Width))
            {
                Border.Width = size.Width - Border.Margin.Left - Border.Margin.Right;
                double width = Border.Width;
                if (!Width.IsAuto)
                {
                    if (!Width.IsRelativeToParagraph)
                        width = Width.Value;
                    else if (Width.Value <= 1)
                        width = size.Width * Width.Value;

                    if (width < Border.Width)
                        Border.Width = width;
                }
                else
                {
                    Border.Width = double.NaN;
                }
            }
            else
            {
                Border.Width = double.NaN;
            }
            Border.Measure(new Size(size.Width, double.PositiveInfinity));
            Border.Arrange(new Rect(Border.DesiredSize));
        }

        #endregion

        #region GetVisual Method

        public virtual UIElement GetVisual(Size constraint)
        {
            // Throw away previous visual. Because it would have been removed from the paragraph,
            // its layout would have been suspended and we cannot measure it anymore
            // So, throw away and get a new one that can be measured
            if (Border != null)
                Border.Child = null;

            Border = new Border();
            Border.Child = Fragment;
            Border.VerticalAlignment = VerticalAlignment.Top;
            Border.HorizontalAlignment = HorizontalAlignment.Left;

            Border.Background = this.Background;
            UpdateVisualMargin(Border);
            UpdateVisualPadding(Fragment);

            Measure(constraint);

            return Border;
        }

        #endregion

        #region GetVisualCopy Method

        public UIElement GetVisualCopy()
        {
            var copy = new Border();
            copy.VerticalAlignment = VerticalAlignment.Top;
            copy.HorizontalAlignment = HorizontalAlignment.Left;
            copy.Background = this.Background;
            copy.Width = Border.RenderSize.Width;
            copy.Height = Border.RenderSize.Height;
            Canvas.SetLeft(copy, Left);

            var context = new HarvestingContext();
            var result = Fragment.Harvest(context, double.PositiveInfinity, null);
            var fragmentCopy = result.HarvestedBlock as Fragment;

            copy.Child = fragmentCopy;
            UpdateVisualMargin(copy);
            UpdateVisualPadding(fragmentCopy);

            return copy;
        }

        protected override void CopyProperties(SmartInline copy)
        {
            base.CopyProperties(copy);

            var result = copy as Figure;
            if (result == null)
                throw new ArgumentException("Copy must be a figure");

            result.Alignment = this.Alignment;
            result.Height = this.Height;
            result.Margin = this.Margin;
            result.Padding = this.Padding;
            result.Width = this.Width;
            result.Left = this.Left;
        }

        #endregion
    }
}
